/*
 * Originally written by Lin, sent as an attachment in an email
 * Modified by Seth to fix Segfault bugs
 *
 * sync.c
 *
 * Synchronization routines for SThread
 */
 

#define _REENTRANT
#include "sthread.h"
#include "sync.h"
#include <stddef.h>
#include <stdlib.h>
#include <sched.h>
#include <stdio.h>

/*
 * Queue routines
 */
 
// NOTE ABOUT QUEUES: 	The last element of queue must be malloc'd space, with ->thread=NULL
//						and ->next = NULL
//						Anywhere from 0 to infinity regular queue elements are allowed before
//						the last element

//						A regular queue element has something in ->thread, and ->next points
//						to either:
//						malloc'd space (if this regular queue element is the 2nd to last element)
//						or the next regular queue element

static int enqueue( Semaphore * s, sthread_t thread ){
	//function modified again by Seth since the first bugfix was buggy
	

	queue_t * t_cur = s->queue;
	if (t_cur->thread) { //SEG: Seth changed t_cur to t_cur->thread
		while(t_cur->next && t_cur->next->thread) {
			//changed by Seth. old while conditions were t_cur->next
			t_cur = t_cur->next;
		}
		//Seth did some rejiggering of everything up to "return 0;" because of segfault
		t_cur = t_cur->next;
		t_cur->thread = thread;
		t_cur->next = ( queue_t * ) malloc( sizeof( queue_t ) );
		
		t_cur->next->thread = NULL;
		t_cur->next->next = NULL;
		return 0;
	}
	else { // else if t_cur->thread == NULL
		t_cur->next = (queue_t *)malloc(sizeof(queue_t));
		t_cur->next->thread = NULL; //added by Seth. very important to do this after mallocing
		t_cur->next->next = NULL; //added by Seth to fix segfault
		t_cur->thread = thread;
	}
	return 0;
}

//dequeue: this function removes first thread from the semaphore's queue
static sthread_t dequeue( Semaphore * s ){
	
	if(!(s->queue->thread)) {
		return NULL;
	}

	sthread_t thread = s->queue->thread;
	queue_t * tt = s->queue->next;
	free( s->queue ); 

	s->queue = tt;
	
	return thread;
}

/*
 * Semaphore routines
 */

int sthread_semaphore_init(Semaphore *s, int count){
	//edit by SETH: removed if count <= 0, return -1;
	 s->count = count;
	 s->guard = 0;
	 s->queue = ( queue_t * ) malloc( sizeof( queue_t ) );
	 s->queue->thread = NULL;
	 s->queue->next = NULL;

	 return 0;
}

int sthread_semaphore_destroy(Semaphore *s){

	s->count = 0;
	s->guard = 0;
	queue_t * queue = s->queue;
	while(queue) {
		queue_t * queue2 = queue;
		queue = queue->next;
		free( queue2 );
	}
	s->queue = NULL;

	return 0;
}

int sthread_semaphore_down(Semaphore *s){
	while( test_and_set( &( s->guard ) ) ) {
		sched_yield( );
	}

	( s->count )--;
	if(s->count < 0) {
		enqueue(s, sthread_self());
		s->guard = 0;
		sthread_suspend();
	}
	else {
		s->guard = 0;
	}

	return 0;
}

int sthread_semaphore_try_down(Semaphore *s){

	while( test_and_set(&(s->guard)) ) {
		sched_yield();
	}

	if( s->count > 0 ) {
		(s->count)--;
		s->guard = 0;
		return 0;
	}
	else {
		s->guard = 0;
		return s->count - 1;
	}
}
int sthread_semaphore_up(Semaphore *s){

	int result = 0;
	while( test_and_set( &( s->guard ) ) ) {
		sched_yield();
	}

	(s->count)++;
	if( s->count <= 0 ) {
		sthread_t thread = dequeue( s );
		if(!thread) {
			s->guard = 0;
			result = -1;
		}
		sthread_wake( thread );
	}
	s->guard = 0;

	return result;
}

/*
 * rwlock routines
 */

int sthread_rwlock_init(sthread_rwlock_t *rwlock){

	sthread_semaphore_init(&rwlock->OkToRead, 0);
	sthread_semaphore_init(&rwlock->OkToWrite, 0);
	sthread_semaphore_init(&rwlock->Lock, 1);
	rwlock->active_readers = 0;
	rwlock->waiting_readers = 0;
	rwlock->active_writers = 0;
	rwlock->waiting_writers = 0;

	return 0;
}

int sthread_rwlock_destroy(sthread_rwlock_t *rwlock){
	sthread_semaphore_destroy(&rwlock->OkToRead);
	sthread_semaphore_destroy(&rwlock->OkToWrite);
	sthread_semaphore_destroy(&rwlock->Lock);
	rwlock->active_readers = 0;
	rwlock->waiting_readers = 0;
	rwlock->active_writers = 0;
	rwlock->waiting_writers = 0;

	return 0;
}

int sthread_read_lock(sthread_rwlock_t *rwlock){
	sthread_semaphore_down(&rwlock->Lock);

	if((rwlock->active_writers + rwlock->waiting_writers) > 0){
		rwlock->waiting_readers++;
		//sthread_semaphore_up(&rwlock->Lock);
		//sthread_semaphore_down(&rwlock->OkToRead);
	}
	else {
		sthread_semaphore_up(&rwlock->OkToRead);
		rwlock->active_readers++;
		//sthread_semaphore_up(&rwlock->Lock);
	}
	sthread_semaphore_up(&rwlock->Lock);
	sthread_semaphore_down(&rwlock->OkToRead);

	return 0;
}

int sthread_read_try_lock(sthread_rwlock_t *rwlock){

	return -1;
}

int sthread_read_unlock(sthread_rwlock_t *rwlock){
	sthread_semaphore_down(&rwlock->Lock);
	rwlock->active_readers--;
	if((rwlock->active_readers == 0) && (rwlock->waiting_writers > 0)) {
		sthread_semaphore_up(&rwlock->OkToWrite);
		rwlock->active_writers++;
		rwlock->waiting_writers--;
	}
	sthread_semaphore_up(&rwlock->Lock);

	return 0;
}

int sthread_write_lock(sthread_rwlock_t *rwlock) {
	sthread_semaphore_down(&rwlock->Lock);
	if((rwlock->active_writers + rwlock->active_readers) > 0){
		rwlock->waiting_writers++;
		//sthread_semaphore_up(&rwlock->Lock);
		//sthread_semaphore_down(&rwlock->OkToWrite);
	}
	else {
		sthread_semaphore_up(&rwlock->OkToWrite);
		rwlock->active_writers++;
		//sthread_semaphore_up(&rwlock->Lock);
	}
	sthread_semaphore_up(&rwlock->Lock);
	sthread_semaphore_down(&rwlock->OkToWrite);

	return 0;
}

int sthread_write_try_lock(sthread_rwlock_t *rwlock) {
	return -1;
}

int sthread_write_unlock(sthread_rwlock_t *rwlock) {

	sthread_semaphore_down(&rwlock->Lock);
	rwlock->active_writers--;
	
	
	if(rwlock->waiting_writers > 0){
		sthread_semaphore_up(&rwlock->OkToWrite);
		rwlock->active_writers++;
		rwlock->waiting_writers--;
	}
	else if (rwlock->waiting_readers > 0){
		while(rwlock->waiting_readers > 0) { //(Seth) I uncommented this while loop to fix a bug.
			// all readers, not just one, should be woken. otherwise a thread might NEVER get a chance
			// to read
			sthread_semaphore_up(&rwlock->OkToRead);
			rwlock->active_readers++;
			rwlock->waiting_readers--;
		}
	}
	sthread_semaphore_up(&rwlock->Lock);

	return 0;
}

